﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

namespace Useful
{
    public class BindingListView<T> : SortableBindingList<T>, IBindingListView
    {
        #region Core
        private bool m_Filtered = false;
        protected string m_FilterString = null;
        private readonly List<T> m_OriginalCollection = new List<T>();

        protected override bool SupportsSearchingCore
        {
            get { return true; }
        }

        protected override int FindCore(PropertyDescriptor property, object key)
        {
            // Simple iteration:
            for (int i = 0; i < Count; i++)
            {
                T item = this[i];
                if (property.GetValue(item).Equals(key))
                {
                    return i;
                }
            }
            return -1; // Not found

            // Alternative search implementation
            // using List.FindIndex:
            //Predicate<T> pred = delegate(T item)
            //{
            // if (property.GetValue(item).Equals(key))
            // return true;
            // else
            // return false;
            //};
            //List<T> list = Items as List<T>;
            //if (list == null)
            // return -1;
            //return list.FindIndex(pred);
        }

        string IBindingListView.Filter
        {
            get
            {
                return m_FilterString;
            }
            set
            {
                m_FilterString = value;
                m_Filtered = true;
                UpdateFilter();
            }
        }

        void IBindingListView.RemoveFilter()
        {
            if (!m_Filtered)
                return;

            m_FilterString = null;
            m_Filtered = false;

            Clear();
            foreach (T item in m_OriginalCollection)
            {
                Add(item);
            }

            m_OriginalCollection.Clear();
        }

        bool IBindingListView.SupportsFiltering
        {
            get
            {
                return true;
            }
        }

        protected virtual string GetCriteria(out string propName)
        {
            propName = null;

            int equalsPos = m_FilterString.IndexOf('=');

            if (equalsPos == -1)
                return null;

            // Get property name
            propName = m_FilterString.Substring(0, equalsPos).Trim();

            // Get filter criteria
            string criteria = m_FilterString.Substring(equalsPos + 1, m_FilterString.Length - equalsPos - 1).Trim();

            // Strip leading and trailing quotes
            criteria = criteria.Substring(1, criteria.Length - 2);

            return criteria;
        }

        protected virtual bool CheckValue(object value, string criteria)
        {
            return value == null || value.ToString().Contains(criteria);
        }

        protected virtual void UpdateFilter()
        {
            string propName;
            string criteria = GetCriteria(out propName);

            if (criteria == null)
                return;

            // Get a property descriptor for the filter property
            PropertyDescriptor propDesc = TypeDescriptor.GetProperties(typeof(T))[propName];

            if (m_OriginalCollection.Count == 0)
            {
                m_OriginalCollection.AddRange(this);
            }

            List<T> currentCollection = new List<T>(m_OriginalCollection);

            Clear();
            foreach (T item in currentCollection)
            {
                object value = propDesc.GetValue(item);

                if (CheckValue(value, criteria))
                {
                    Add(item);
                }
            }
        }
        #endregion


        public class PropertyComparer<T> : IComparer<T>
        {
            private PropertyDescriptor _property;
            private ListSortDirection _direction;

            public PropertyComparer(PropertyDescriptor property,
                ListSortDirection direction)
            {
                _property = property;
                _direction = direction;
            }

            #region IComparer<T>

            public int Compare(T xWord, T yWord)
            {
                // Get property values
                object xValue = GetPropertyValue(xWord, _property.Name);
                object yValue = GetPropertyValue(yWord, _property.Name);

                // Determine sort order
                if (_direction == ListSortDirection.Ascending)
                {
                    return CompareAscending(xValue, yValue);
                }
                else
                {
                    return CompareDescending(xValue, yValue);
                }
            }

            public bool Equals(T xWord, T yWord)
            {
                return xWord.Equals(yWord);
            }

            public int GetHashCode(T obj)
            {
                return obj.GetHashCode();
            }

            #endregion

            // Compare two property values of any type
            private int CompareAscending(object x, object y)
            {
                int result;

                // If values implement IComparer
                if (x is IComparable)
                {
                    result = ((IComparable)x).CompareTo(y);
                }
                // If values don't implement IComparer but are equivalent
                else if (x.Equals(y))
                {
                    result = 0;
                }
                // Values don't implement IComparer and are not equivalent,
                // so compare as typed values
                else result = ((IComparable)x).CompareTo(y);

                // Return result
                return result;
            }

            private int CompareDescending(object x, object y)
            {
                // Return result adjusted for ascending or descending sort order ie
                // multiplied by 1 for ascending or -1 for descending
                return -CompareAscending(x, y);
            }

            private object GetPropertyValue(T value, string property)
            {
                // Get property
                PropertyInfo propertyInfo = value.GetType().GetProperty(property);

                // Return value
                return propertyInfo.GetValue(value, null);
            }
        }

        private ListSortDescriptionCollection _SortDescriptions;

        private List<PropertyComparer<T>> comparers;

        public ListSortDescriptionCollection SortDescriptions
        {
            get { return _SortDescriptions; }
        }

        private int CompareValuesByProperties(T x, T y)
        {
            if (x == null)
                return (y == null) ? 0 : -1;
            else
            {
                if (y == null)
                    return 1;
                else
                {
                    foreach (PropertyComparer<T> comparer in comparers)
                    {
                        int retval = comparer.Compare(x, y);
                        if (retval != 0)
                            return retval;
                    }
                    return 0;
                }
            }

        }


        #region IBindingListView Members

        public void ApplySort(ListSortDescriptionCollection sorts)
        {
            // Get list to sort
            // Note: this.Items is a non-sortable ICollection<T>
            List<T> items = this.Items as List<T>;

            // Apply and set the sort, if items to sort
            if (items != null)
            {
                _SortDescriptions = sorts;
                comparers = new List<PropertyComparer<T>>();
                foreach (ListSortDescription sort in sorts)
                    comparers.Add(new PropertyComparer<T>(sort.PropertyDescriptor,
                        sort.SortDirection));
                items.Sort(CompareValuesByProperties);
                //_isSorted = true;
            }
            else
            {
                //_isSorted = false;
            }

            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        public bool SupportsAdvancedSorting
        {
            get { throw new System.NotImplementedException(); }
        }

        #endregion
    }
}
